#include <inc/types.h>
#include <inc/svm.h>
#include <inc/stdio.h>
#include <inc/x86.h>
#include <kern/console.h>
#include <boot/bootparam.h>
#include <inc/apic.h>


/*
 * preparing the stack for the guest to run
 * vmrun 
 * exit from the guest and restore the host state
 * code is copied from xen and corey
 */




struct vmcb vmcb_test[32] __attribute__((aligned (4096), section (".data")));
vm_frame vfrm[32] __attribute__((aligned (4096), section (".data")));
char host_save[4096*32] __attribute__((aligned (4096), section (".data")));


void msr_inter(void *pm, uint32_t msr, uint32_t acc)
{
    uint32_t base = 0x0, index = 0x0, offset = 0x0;

    uint8_t *map = (uint8_t *) pm;
    for(uint32_t i =  0; i < 8192; i ++)
	map[i] = 0x0;

    if(msr >= 0xc0000000 && msr < 0xc0010000)
	base = 0x800;
    if(msr >= 0xc0001000)
	base = 0x1000;
	
    index = msr&0xffff;
    offset = (index * 2)%8;
    index = (index * 2)/8;
    map = map + base + index;
    *map |= acc<<offset;
}


void wrmsr_inter(void *frame, void *vmcb)
{
    vm_frame *vf;
    struct vmcb *vb;
    uint32_t eax, edx, ecx;

    vf = frame;
    vb = vmcb;
    edx = 0xc0000080;
    eax = vb->rax;
    edx = vf->gregs.gregs[3];
    eax |= 0x1000;
    vb->efer = (((uint64_t) edx) << 32) | ((uint64_t) eax);
    vb->rip += 2;
}

void rest_stack_run_svm(struct vmcb *vmcb, vm_frame *vfrm1)
{
    uint64_t guest_stack = (uint64_t )vfrm1;
    uint64_t vmcb_phya = (uint64_t)vmcb;
    lock_cprintf("vmcb phys is 0x%x, vframe phys is 0x%x\n", vmcb_phya, guest_stack);
	
    __asm __volatile(
		     "mov %0, %%rsp \n\t"
		     "mov %1, %%rdi \n\t"
		     "jmp svm_run"
		     ::"r"(guest_stack),"r"(vmcb_phya)
		     );
}

void start_svm()
{
    extern struct boot_params *boot_params;
    uint32_t procid;
    char *svm_sig = (char *)0x400fe;
    svm_sig[0] = '^';
    procid = lapicid() - 0x4;
	
    struct vmcb *vmcb = &vmcb_test[procid];

    char *p = (char *)vmcb;

    lock_cprintf("host save addr is 0x%x, in proc %d\n", (uint64_t) host_save +
		 procid*4096, procid); 
    lock_cprintf("svm initial exit code is %ld, in proc %d\n", vmcb->exitcode, procid);
	
    write_msr(0xc0010117, ((uint64_t) host_save) + procid*4096);
	

    for(uint32_t i = 0; i < sizeof(struct vmcb); i ++)
	p[i] = 0;
	
	
    svm_vmcb_init(vmcb);
    if(procid != 0)
    {
	uint64_t val = read_msr(0xc0010114);
	write_msr(0xc0010114, val | 0x2);
	vmcb->rip = 0x15c;
	vmcb->exc_intercepts |= 1<<30; 
	vmcb->intercepts1 |= INTERCEPT_MSR_PROT;
	vmcb->msrpm_base_pa = (uint64_t)msrpm + 8192 * procid;
	msr_inter((void *)vmcb->msrpm_base_pa, 0xc0000080, 0x2);
    }
    if(procid == 0)
    {
	uint64_t jj = 1000000000;
	while(jj --)
	    __asm__ __volatile__("pause");
    }
	
    vfrm[procid].gregs.gregs[1] = (uint64_t)boot_params;
    vfrm[procid].gregs.gregs[14] = (uint64_t)vmcb;
    rest_stack_run_svm(vmcb, &vfrm[procid]);
}

void svm_exit_handler()
{
    struct vmcb *vmcb;
    
    __asm __volatile("mov %%rax, %0":"=a"(vmcb)::"cc");
    
    uint32_t procid;
    procid = lapicid() - 4;
    
    lock_cprintf("vmcb base is %lx\n", (uint64_t)vmcb);
    lock_cprintf("hello world I'm VMM in proc %d\n", procid);
    lock_cprintf("svm exit code is 0x%x, info1 is 0x%x\n",vmcb->exitcode,
		 vmcb->exitinfo1);
    
    if(vmcb->exitcode == 0x5e)
    {
	uint64_t val = read_msr(0xc0010114);
	val &= ~0x2;
	write_msr(0xc0010114, val);
	uint16_t *vec = (uint16_t *)0x467;
	uint32_t cs = vec[1];
	vmcb->cs.sel = cs;
	vmcb->rip = 0;
	vmcb->cs.base = cs<<4;
	vmcb->efer = 0x1000;
	
	//uint16_t *vec = (uint16_t *)0x467;
	//vec[1] = 0x4000;
	return;
    }
    /*
      cprintf("svm exit , vmcb address is 0x%x\n", (uint64_t) vmcb);
      cprintf("svm evnvi type is 0x%x, errocode is 0x%x, vector is 0x%x\n"
      , vmcb->exitinfo.type, vmcb->exitinfo.errorcode, vmcb->exitinfo.vector);
      cprintf("guest exit with cs: 0x%x, rip: 0x%x\n", vmcb->cs.sel, vmcb->rip);
    */	

        
    if(vmcb->exitcode == 0x81)
    {
	vmcb->rip += 3;
	vfrm[procid].gregs.gregs[0] = 0xaa55;
    }
    if(vmcb->exitcode == 0x7c)
    {
	lock_cprintf("efer write intercept, msr addr is %x\n", vfrm[procid].gregs.gregs[2]);
	wrmsr_inter(&vfrm[procid], vmcb);
    }
    if(vmcb->exitcode == 0x5e)
    {
	uint64_t val = read_msr(0xc0010114);
	val &= ~0x2;
	write_msr(0xc0010114, val);
		
	uint16_t *vec = (uint16_t *)0x467;
	uint32_t cs = vec[1];
	vmcb->cs.sel = cs;
	vmcb->rip = 0;
	vmcb->cs.base = cs<<4;
    }
}

